﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace QbiconEditor
{
    public static class QGraphicsHelper
    {
        public const float PIDiv180 = 0.017453292519943295769236907684886f;

        public static QPreferences Preferences;
        public static float ZoomAspect = 1;
        public static QImageList Textures = new QImageList();

        public static QRectangleF ViewRect = new QRectangleF() { Left = 0, Top = 0, Width = 100, Height = 100 };
  
        public static RectangleF GetRectOnScreen(RectangleF rect)
        {
            QRectangleF qr = new QRectangleF() { Left = rect.Left, Top = rect.Top, Width = rect.Width, Height = rect.Height };
            qr = GetQRectOnScreen(qr);
            return
              new RectangleF(
                qr.Left,
                qr.Top,
                qr.Width,
                qr.Height
              );
        }

        public static RectangleF GetRectOnMap(RectangleF rect)
        {
            QRectangleF qr = new QRectangleF() { Left = rect.Left, Top = rect.Top, Width = rect.Width, Height = rect.Height };
            qr = GetRectOnMap(qr);
            
            return
              new RectangleF(
                qr.Left,
                qr.Top,
                qr.Width,
                qr.Height
              );
        }

        public static Rectangle GetRectOnScreen(QRectangleF rect)
        {
            QRectangleF r = GetQRectOnScreen(rect);

            return new Rectangle((int)r.Left, (int)r.Top, (int)r.Width, (int)r.Height);
        }

        public static QRectangleF GetQRectOnScreen(QRectangleF rect)
        {
            QRectangleF sRect = new QRectangleF();
            QPoint TopLeft = GetPointOnScreen(rect.Left, rect.Top);
            QPoint RightBottom = GetPointOnScreen(rect.Left + rect.Width, rect.Top + rect.Height);

            sRect.Left = TopLeft.X;
            sRect.Top = TopLeft.Y;
            sRect.Width = RightBottom.X - TopLeft.X;
            sRect.Height = RightBottom.Y - TopLeft.Y;

            return FixRectangle(sRect);
        }

        public static QPolygone GetPolyOnScreen(QPolygone poly)
        {
            QPolygone newPoly = new QPolygone();

            foreach (QPoint qp in poly.Points)
            {
                newPoly.Points.Add(GetPointOnScreen(qp));
            }

            return newPoly;
        }

        public static QRectangleF GetRectOnMap(QRectangleF rect)
        {
            QRectangleF sRect = new QRectangleF();

            QPoint TopLeft = GetPointOnMap(rect.Left, rect.Top);
            QPoint RightBottom = GetPointOnMap(rect.Left + rect.Width, rect.Top + rect.Height);

            sRect.Left = TopLeft.X;
            sRect.Top = TopLeft.Y;
            sRect.Width = RightBottom.X - TopLeft.X;
            sRect.Height = RightBottom.Y - TopLeft.Y;

            return sRect;
        }

        public static QPoint GetPointOnMap(float pX, float pY)
        {
            float tY = (pY / ZoomAspect) + ViewRect.Top;

            return
              new QPoint()
              {
                  X = (pX / ZoomAspect) + ViewRect.Left,
                  Y = Preferences.TopOnBottom ? tY : -tY
              };

        }

        public static QPoint GetPointOnScreen(QPoint point)
        {
            if (point != null)
            {
                return GetPointOnScreen(point.X, point.Y);
            }
            else
            {
                return null;
            }
        }

        public static QPoint GetPointOnScreen(float pX, float pY)
        {
            pY = QGraphicsHelper.Preferences.TopOnBottom ? pY : -pY;
            
            return
              new QPoint()
              {
                  X = (pX - ViewRect.Left) * ZoomAspect,
                  Y = (pY - ViewRect.Top) * ZoomAspect
              };
        }

        public static PointF GetPointOnScreen(PointF point)
        {
            point.Y = QGraphicsHelper.Preferences.TopOnBottom ? point.Y : -point.Y;

            return
              new PointF(
                 (point.X - ViewRect.Left) * ZoomAspect,
                 (point.Y - ViewRect.Top) * ZoomAspect
            );
        }

        public static Image FindTexture(string name)
        {
            if (string.IsNullOrEmpty(name) == false)
            {
                if (Textures != null)
                {
                    if (Textures.OriginalImages != null)
                    {

                        if (Textures.OriginalImages.ContainsKey(name) == true)
                        {
                            return Textures.OriginalImages[name];
                        }

                    }
                }
            }

            return null;
        }

        public static QRectangleF QRectFromRectF(RectangleF rectF)
        {
            return NewQRectF(rectF.Left, rectF.Top, rectF.Width, rectF.Height);
        }

        public static RectangleF[] GetFrameRectsInPoints(PointF[] points, int rectWidth)
        {
            List<RectangleF> rects = new List<RectangleF>();

            foreach (PointF p in points)
            {
                QPoint qp = new QPoint() { X = p.X, Y = p.Y };

                rects.Add(GetFrameRectInPoint(qp, Preferences.PointFrameRectWidth));
            }

            return rects.ToArray();
        }

        public static RectangleF[] GetFrameRectsOnScreenInPoints(QPoint[] points, int rectWidth)
        {
            List<RectangleF> rects = new List<RectangleF>();

            foreach (QPoint qp in points)
            {
                rects.Add(GetFrameRectInPoint(GetPointOnScreen(qp), Preferences.PointFrameRectWidth));
            }

            return rects.ToArray();
        }

        public static RectangleF GetFrameRectInPoint(QPoint point, int rectWidth)
        {
            if (point != null)
            {
                return
                    new RectangleF(point.X - rectWidth,
                                   point.Y - rectWidth,
                                    rectWidth * 2,
                                    rectWidth * 2);
            }

            return new RectangleF();
        }

        public static QRectangleF NewQRectF(QRectangleF rect)
        {
            return new QRectangleF() { Left = rect.Left, Top = rect.Top, Width = rect.Width, Height = rect.Height };
        }

        public static QRectangle NewQRect(int left, int top, int width, int height)
        {
            return new QRectangle() { Left = left, Top = top, Width = width, Height = height };
        }

        public static QRectangle NewQRect(Rectangle rect)
        {
            return new QRectangle() { Left = rect.Left, Top = rect.Top, Width = rect.Width, Height = rect.Height };
        }

        public static QRectangleF NewQRectF(RectangleF rect)
        {
           return new QRectangleF() { Left = rect.Left, Top = rect.Top, Width = rect.Width, Height = rect.Height };
        }

        public static QRectangleF NewQRectF(float left, float top, float width, float height)
        {
            return new QRectangleF() { Left = left, Top = top, Width = width, Height = height };
        }

        public static bool IsPointInRect(float pX, float pY, RectangleF rect)
        {
            return IsPointInRect(pX, pY, rect.Left, rect.Top, rect.Width, rect.Height);
        }

        public static bool IsPointInRect(float pX, float pY, QRectangleF rect)
        {
            return IsPointInRect(pX, pY, rect.Left, rect.Top, rect.Width, rect.Height);
        }

        public static bool IsPointInObjects(List<QObject> qobs, QPoint qp)
        {
            return IsPointInObjects(qobs, qp.X, qp.Y) != null;
        }

        public static QObject IsPointInObjects(List<QObject> qobs, float pX, float pY)
        {
            foreach (QObject qob in qobs)
            {
                if (IsPointInPolygone(new QPoint() { X = pX, Y = pY }, qob.Poly.Points.ToArray()))
                {
                    return qob;
                }
            }

            return null;
        }

        public static bool IsPointInRect(float pX, float pY, float rLeft, float rTop, float rWidth, float rHeight)
        {
            return (
                     pX >= rLeft &&
                     pX <= rLeft + rWidth &&
                     pY >= rTop &&
                     pY <= rTop + rHeight
                   );
        }

        public static bool IsRect1VisibleInRect2(float r1Left, float r1Top, float r1Width, float r1Height, float r2Left, float r2Top, float r2Width, float r2Height)
        {
            return (
                     IsPointInRect(r1Left, r1Top, r2Left, r2Top, r2Width, r2Height) ||
                     IsPointInRect(r1Left + r1Width, r1Top + r1Height, r2Left, r2Top, r2Width, r2Height) ||
                     IsPointInRect(r1Left + r1Width, r1Top, r2Left, r2Top, r2Width, r2Height) ||
                     IsPointInRect(r1Left, r1Top + r1Height, r2Left, r2Top, r2Width, r2Height)
                   );

        }

        public static bool IsPolyInRect(QPolygone poly, QRectangleF rect)
        {
            foreach (QPoint qp in poly.Points)
            {
                if (IsPointInRect(qp.X, qp.Y, rect) == false)
                {
                    return false;
                }
            }

            return true;
        }

        public static bool IsRect1FullyInRect2(QRectangleF rect1, QRectangleF rect2)
        {
            return
              IsRect1FullyInRect2(rect1.Left, rect1.Top, rect1.Width, rect1.Height,
                                  rect2.Left, rect2.Top, rect2.Width, rect2.Height);
        }

        public static bool IsRect1FullyInRect2(float r1Left, float r1Top, float r1Width, float r1Height, float r2Left, float r2Top, float r2Width, float r2Height)
        {
            return
                (r1Left >= r2Left) && (r1Left <= r2Left + r2Width) &&
                (r1Top >= r2Top) && (r1Top <= r2Top + r2Height) &&
                (r1Width <= r2Width) && (r1Height <= r2Height);
        }

        public static QObject IsPointInCornersOfObjects(List<QObject> qobs, float pX, float pY)
        {
            foreach (QObject qob in qobs)
            {
                QPoint qp = IsPointInCornerRects(qob.Poly.Points.ToArray(), pX, pY);

                if (qp != null)
                {
                    return qob;
                }
            }

            return null;
        }

        public static QPoint IsPointInCornersOfObject(QObject qob, float pX, float pY)
        {
            if (qob == null)
            {
                return null;
            }

            return IsPointInCornerRects(qob.Poly.Points.ToArray(), pX, pY);
        }

        public static QPoint IsPointInCornerRects(QPoint[] points, QPoint point)
        {
            return IsPointInCornerRects(points, point.X, point.Y);
        }

        public static QPoint IsPointInCornerRects(QPoint[] points, float pX, float pY)
        {
            foreach (QPoint qp in points)
            {
                if (IsPointInRect(pX, pY, GetFrameRectInPoint(GetPointOnScreen(qp.X, qp.Y), Preferences.PointFrameRectWidth)) == true)
                {
                    return qp;
                }
            }

            return null;
        }

        public static void NavigateToPoint(float pX, float pY)
        {
            if (Preferences.TopOnBottom == false)
            {
                pY = -pY;
            }

            ViewRect.Left = (pX - (ViewRect.Width / 2));
            ViewRect.Top = (pY - (ViewRect.Height / 2));

        }

        public static void NavigateToObject(QObject qObj)
        {
            if (qObj != null)
            {
                QPoint qp = GetPolygoneCenter(qObj.Poly);
                NavigateToPoint(qp.X, qp.Y);
            }
        }

        public static bool ChangeZoom(float aspect)
        {
            float newZoomValue = ZoomAspect + aspect;

            if ((newZoomValue > 1) && (newZoomValue < QbiconConstants.MAX_ZOOM_VALUE))
            {
                ZoomAspect += aspect;

                return true;
            }
            else
            {
                return false;
            }
        }

        public static QRectangleF FixRectangle(QRectangleF rect)
        {
            QRectangleF newRect = NewQRectF(rect.Left, rect.Top, rect.Width, rect.Height);

            if (rect.Width < 0)
            {
                newRect.Left += rect.Width;
                newRect.Width = Math.Abs(rect.Width);
            }

            if (rect.Height < 0)
            {
                newRect.Top += rect.Height;
                newRect.Height = Math.Abs(rect.Height);
            }

            return newRect;
        }

        public static Rectangle FixRectangle(Rectangle rect)
        {
            QRectangleF qr = FixRectangle(NewQRectF(rect));

            return new Rectangle((int)qr.Left, (int)qr.Top, (int)qr.Width, (int)qr.Height);
        }

        public static RectangleF FixRectangle(RectangleF rect)
        {
            QRectangleF qr = FixRectangle(NewQRectF(rect));

            return new RectangleF(qr.Left, qr.Top, qr.Width, qr.Height);
        }

        public static QPolygone GetPolyFromRect(QRectangleF rect)
        {
            QPolygone qp = new QPolygone();

            qp.Points.Add(new QPoint() { X = rect.Left, Y = rect.Top });
            qp.Points.Add(new QPoint() { X = rect.Left + rect.Width, Y = rect.Top });
            qp.Points.Add(new QPoint() { X = rect.Left + rect.Width, Y = rect.Top + rect.Height });
            qp.Points.Add(new QPoint() { X = rect.Left, Y = rect.Top + rect.Height });

            return qp;
        }

        public static QRectangleF GetRectFromPoly(QPolygone poly)
        {
            QRectangleF qr = NewQRectF(poly.Points[0].X,
                                     poly.Points[0].Y,
                                     poly.Points[2].X - poly.Points[0].X,
                                     poly.Points[2].Y - poly.Points[0].Y);

            return qr;

        }

        public static Point QPointToPoint(QPoint qp)
        {
            return new Point { X = (int)qp.X, Y = (int)qp.Y };
        }
        public static PointF QPointToPointF(QPoint qp)
        {
            return new PointF { X = qp.X, Y = qp.Y };
        }

        public static PointF[] QPointsToScreenPoints(QPoint[] qps)
        {
            List<PointF> res = new List<PointF>();

            foreach (QPoint qp in qps)
            {
                res.Add(QPointToPointF(GetPointOnScreen(qp)));
            }

            return res.ToArray();
        }

        public static bool IsLineIntersectRect(float x1, float y1, float x2, float y2, QRectangleF rect)
        {
            float dx, dy, y, x, j, mn;

            dx = Math.Abs(x2 - x1);
            dy = Math.Abs(y2 - y1);

            mn = 1;

            if (dx < 1)
            {
                mn = 0.1f;
            }

            if (dx < 0.1f)
            {
                mn = 0.01f;
            }

            if (dx >= dy)
            {
                if (x1 < x2)
                {
                    int bx = (int)(x1 * mn);
                    int ex = (int)(x2 * mn);

                    for (int i = bx; i < ex; i++)
                    {
                        j = i * mn;
                        y = ((y2 - y1) / (x2 - x1)) * j + (y1 - x1 * ((y2 - y1) / (x2 - x1)));

                        if (IsPointInRect(j, y, rect) == true)
                        {
                            return true;
                        }
                    }
                }
                else if (x2 < x1)
                {
                    int bx = (int)(x2 * mn);
                    int ex = (int)(x1 * mn);

                    for (int i = bx; i < ex; i++)
                    {
                        j = i * mn;
                        y = ((y2 - y1) / (x2 - x1)) * j + (y1 - x1 * ((y2 - y1) / (x2 - x1)));

                        if (IsPointInRect(j, y, rect) == true)
                        {
                            return true;
                        }
                    }
                }
            }

            mn = 1;

            if (dy < 1)
            {
                mn = 0.1f;
            }

            if (dy < 0.1f)
            {
                mn = 0.01f;
            }

            if (dx < dy)
            {
                if (y1 < y2)
                {
                    int by = (int)(y1 * mn);
                    int ey = (int)(y2 * mn);

                    for (int i = by; i < ey; i++)
                    {
                        j = i * mn;
                        x = ((x2 - x1) / (y2 - y1)) * j + (x1 - y1 * ((x2 - x1) / (y2 - y1)));

                        if (IsPointInRect(x, j, rect) == true)
                        {
                            return true;
                        }
                    }
                }
                else if (y2 < y1)
                {
                    int by = (int)(y2 * mn);
                    int ey = (int)(y1 * mn);

                    for (int i = by; i < ey; i++)
                    {
                        j = i * mn;
                        x = ((x2 - x1) / (y2 - y1)) * j + (x1 - y1 * ((x2 - x1) / (y2 - y1)));

                        if (IsPointInRect(x, j, rect) == true)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        public static QPoint[] IsRectInLinesBetweenPoints(RectangleF rect, QPoint[] points, bool onScreen)
        {
            QRectangleF qrect = NewQRectF(rect.X, rect.Y, rect.Width, rect.Height);

            return IsRectInLinesBetweenPoints(qrect, points, onScreen);
        }

        public static QPoint[] IsRectInLinesBetweenPoints(QRectangleF rect, QPoint[] points, bool onScreen)
        {
            if (points.Length < 2)
            {
                return null;
            }

            QPoint qp1;
            QPoint qp2;

            for (int i = 0; i < points.Length - 1; i++)
            {
                qp1 = points[i];
                qp2 = points[i + 1];

                if (onScreen == true)
                {
                    qp1 = GetPointOnScreen(qp1);
                    qp2 = GetPointOnScreen(qp2);
                }

                if (IsLineIntersectRect(qp1.X, qp1.Y, qp2.X, qp2.Y, rect) == true)
                {
                    return new QPoint[] { points[i], points[i + 1] };
                }
            }

            //also check line between last and fist points
            qp1 = points[0];
            qp2 = points[points.Length - 1];

            if (onScreen == true)
            {
                qp1 = GetPointOnScreen(qp1);
                qp2 = GetPointOnScreen(qp2);
            }

            if (IsLineIntersectRect(qp1.X, qp1.Y, qp2.X, qp2.Y, rect) == true)
            {
                return new QPoint[] { points[0], points[points.Length - 1] };
            }

            return null;
        }

        public static QRectangleF GetPolygoneRect(QPolygone poly)
        {
            QPoint TopLeft = new QPoint() { X = float.MaxValue, Y = float.MaxValue };
            QPoint BottomRight = new QPoint() { X = float.MinValue, Y = float.MinValue };

            foreach (QPoint qp in poly.Points)
            {
                if (qp.X < TopLeft.X)
                {
                    TopLeft.X = qp.X;
                }

                if (qp.X > BottomRight.X)
                {
                    BottomRight.X = qp.X;
                }

                if (qp.Y < TopLeft.Y)
                {
                    TopLeft.Y = qp.Y;
                }

                if (qp.Y > BottomRight.Y)
                {
                    BottomRight.Y = qp.Y;
                }
            }

            return new QRectangleF()
            {
                Left = TopLeft.X,
                Top = TopLeft.Y,
                Width = BottomRight.X - TopLeft.X,
                Height = BottomRight.Y - TopLeft.Y
            };

        }

        public static QPoint GetPolygoneCenter(QPolygone poly)
        {
            QRectangleF r = GetPolygoneRect(poly);

            return new QPoint() { X = r.Left + (r.Width / 2), Y = r.Top + (r.Height / 2) };
        }

        public static bool IsPointInPolygone(QPoint p, QPoint[] poly)
        {
            QPoint p1, p2;

            bool inside = false;

            if (poly.Length < 3)
            {
                return inside;
            }

            QPoint oldPoint = new QPoint() { X = poly[poly.Length - 1].X, Y = poly[poly.Length - 1].Y };

            for (int i = 0; i < poly.Length; i++)
            {
                QPoint newPoint = new QPoint() { X = poly[i].X, Y = poly[i].Y };

                if (newPoint.X > oldPoint.X)
                {
                    p1 = oldPoint;
                    p2 = newPoint;
                }
                else
                {
                    p1 = newPoint;
                    p2 = oldPoint;
                }

                if ((newPoint.X < p.X) == (p.X <= oldPoint.X) && (p.Y - p1.Y) * (p2.X - p1.X) < (p2.Y - p1.Y) * (p.X - p1.X))
                {
                    inside = !inside;
                }

                oldPoint = newPoint;
            }

            return inside;
        }

        public static void ScalePoly(QPolygone poly, float scaleAspect)
        {
            QRectangleF rect = GetPolygoneRect(poly);

            float rCenterX = rect.Left + rect.Width / 2;
            float rCenterY = rect.Top + rect.Height / 2;

            if ((rect.Width > 0.1) && (rect.Height > 0.1))
            {
                foreach (QPoint qp in poly.Points)
                {
                    if (scaleAspect > 0)
                    {
                        qp.X = ((qp.X - rCenterX) * scaleAspect) + rCenterX;
                        qp.Y = ((qp.Y - rCenterY) * scaleAspect) + rCenterY;
                    }
                    else
                    {
                        qp.X = ((qp.X - rCenterX) / -scaleAspect) + rCenterX;
                        qp.Y = ((qp.Y - rCenterY) / -scaleAspect) + rCenterY;
                    }
                }
            }
        }

        public static void RotatePoly(QPolygone poly, float angle)
        {
            QPoint center = GetPolygoneCenter(poly);

            foreach (QPoint qp in poly.Points)
            {
                RotatePoint(qp, center, angle);
            }
        }

        public static void RotatePoint(QPoint point, QPoint center, float angle)
        {
            angle = angle * PIDiv180;

            float px = point.X - center.X;
            float py = point.Y - center.Y;

            float xnew = (float)(px * Math.Cos(angle) - py * Math.Sin(angle));
            float ynew = (float)(py * Math.Cos(angle) + px * Math.Sin(angle));

            point.X = xnew + center.X;
            point.Y = ynew + center.Y;
        }
        public static QRectangleF InvertRect(QRectangleF rect)
        {
            return NewQRectF(rect.Left, rect.Top - rect.Height, rect.Width, rect.Height);
        }
        public static QRectangle FixTextureRect(QRectangle rect)
        {
            return rect;
        }
    }
}
